from bcat.util.assert_or_raise import AssertOrRaise
from bcat.gtp.serde.error import UnexpectEndOfStreamError \
    , NormalByteAfterEscapeError, SpecialByteWithoutEscapeError

class EscapeTable(object):
    @staticmethod 
    def from_str(s, escape_char, encoding="utf-8"):
        AssertOrRaise(s)\
            .not_none().is_type(str)
        AssertOrRaise(escape_char)\
            .not_none().is_type(str) \
            .length().equal_to(1)

        return EscapeTable(s.encode(encoding), escape_char.encode(encoding)[0])

    def __init__(self, bytes_chars, escape_sign):
        AssertOrRaise(bytes_chars) \
            .not_none().is_types((bytes, bytearray, memoryview))

        AssertOrRaise(escape_sign)  \
            .not_none().is_type(int)

        self.__tab = [False for x in range(256) ]
        for e in bytes_chars:
            self.__tab[e] = True
        self.__escape_sign = escape_sign

    def get(self, u8):
        return self.__tab[u8]

    def __getitem__(self, index):
        if index >= 0 and index <= 255:
            return self.get(index)
        else:
            raise IndexError("[0..256) but:{index}")

    #byte is special byte , which need to be escape
    def is_special_byte(self, u8):
        return self.get(u8)
    def is_normal_byte(self, u8):
        return self.get(u8) == False

    def is_escape_byte(self, u8):
        return self.__escape_sign == u8
    def escape_byte(self):
        return self.__escape_sign


class Escape(object):
    @staticmethod 
    def from_str_gbk(s):
        AssertOrRaise(s).not_none("need type str").is_type(str) 
        return Escape(s.encode("gbk"))

    @staticmethod
    def from_str(s, encoding="utf-8"): 
        AssertOrRaise(s).not_none("need type str").is_type(str) 
        return Escape(s.encode(encoding))

    @staticmethod
    def from_bytes(bs):
        AssertOrRaise(bs).not_none("need type buffer").is_types((bytes, bytearray, memoryview))
        return Escape(bs)

    ESCAPE_TAB = EscapeTable(b"=,[]{}\\", b'\\'[0])

    def __init__(self, bs):
        self.__bytes = bs

    def escape(self):
        result = bytearray()
        for e in self.__bytes:
            if Escape.ESCAPE_TAB.is_special_byte(e):
                result.append(Escape.ESCAPE_TAB.escape_byte())
            result.append(e)
        return result

    def unescape(self):
        result = bytearray()

        index = 0
        l = len(self.__bytes)
        while index < l:
            e =  self.__bytes[index]
            if Escape.ESCAPE_TAB.is_normal_byte(e):
                result.append(e)
                index += 1
            elif Escape.ESCAPE_TAB.is_escape_byte(e): #'\\'
                index += 1
                if index >= l:
                    raise UnexpectEndOfStreamError(index)
                
                next = self.__bytes[index]
                if Escape.ESCAPE_TAB.is_special_byte(next):
                    result.append(next)
                    index += 1
                else:
                    raise NormalByteAfterEscapeError(index)
            else:
                raise SpecialByteWithoutEscapeError(index)
        return result


import unittest
class TestEscapeTable(unittest.TestCase):
    def test_main(self):
        special_signs = "=,[]{}\\"
        escape_sign = "\\"
        escape_sign_byte = escape_sign.encode()[0]
        #tab = EscapeTable(b"=,[]{}\\", b"\\")
        tab = EscapeTable.from_str(special_signs, escape_sign)

        self.assertTrue(tab.is_special_byte(b'='[0]))

        self.assertTrue(tab.is_escape_byte(escape_sign_byte))
        self.assertEqual(escape_sign_byte, tab.escape_byte())

        for e in special_signs.encode():
            self.assertTrue(tab.is_special_byte(e))
            self.assertTrue(tab.get(e))

        self.assertFalse(tab.get(1))
        self.assertFalse(tab.get(255))

        self.assertFalse(tab[0])
        self.assertTrue(tab[ord(b'=')])



class TestEscape(unittest.TestCase):
    def test_escape_bytes(self):
        bs = b'123333'
        result = Escape.from_bytes(bs).escape()
        self.assertEqual(len(bs), len(result))
        self.assertEqual(bs, result)


        bs  = b"1,2=3{4}5[6]7\\89"
        expect_bs = b"1\\,2\\=3\\{4\\}5\\[6\\]7\\\\89"
        result = Escape.from_bytes(bs).escape()
        self.assertEqual(len(expect_bs), len(result))
        self.assertEqual(expect_bs, result)

    def test_escape_gbk_str(self):
        s = "123333"
        expect_s = s.encode('gbk')
        result = Escape.from_str_gbk(s).escape()
        self.assertEqual(expect_s, result)

class TestUnescape(unittest.TestCase):
    def test_unescape_bytes(self):
        bs = b"123333hahahahhaha"
        expect_bs = bs
        result = Escape(bs).unescape()
        self.assertEqual(expect_bs, result)


        bs = b"1\\,2\\=3\\{4\\}5\\[6\\]7\\\\89"
        expect_bs  = b"1,2=3{4}5[6]7\\89"
        result = Escape(bs).unescape()
        self.assertEqual(expect_bs, result)

        bs = b"\\"
        with self.assertRaisesRegex(UnexpectEndOfStreamError, "end of stream") as ctx_m:
            Escape(bs).unescape()
        self.assertEqual(1, ctx_m.exception.offset())
        
        
        s = [b",", b"=", b"{", b"}", b"[", b"]"]
        for e in s:
            with self.assertRaisesRegex(SpecialByteWithoutEscapeError, "special byte with no escape") as ctx_m:
                Escape(e).unescape()
            self.assertEqual(0, ctx_m.exception.offset())

        bs = b"\\1"
        with self.assertRaisesRegex(NormalByteAfterEscapeError, "normal byte after escape") as ctx_m:
            Escape(bs).unescape()
        self.assertEqual(1, ctx_m.exception.offset())

if __name__ == '__main__':
    unittest.main(verbosity=2)
